﻿namespace BattleCity.GameLogic

open System
open FMath
open UI
open Types
open Casanova
open GameState

//****************************************************************************************//
//                                 BULLETS COLLISIONS                                     //
//****************************************************************************************//

module BulletsCollisionsFunctions =

 (*---------------------------------------------------------------------------
    Data la posizione di un proiettile, verifica se esce dai confini della   
    mappa. 
    
    Restituisce un booleano:
    true -> il proiettile è uscito dalla mappa
    false -> il proiettile non è uscito dalla mappa                                       
  ----------------------------------------------------------------------------*)

 let mapBoundingForBullets = mapBounding + 6.0<m>

 let bulletOutofMap(position : Vector2<m>) =
  let mutable intersection = false
  if mapBoundingForBullets < position.X || mapBoundingForBullets < position.Y then intersection <- true
  if -mapBoundingForBullets > position.X || -mapBoundingForBullets > position.Y then intersection <- true
  intersection

 (*---------------------------------------------------------------------------
    Prende in ingresso lo stato di un proiettile e verifica se interseca      
    dei blocchi di mattoni o di metallo della mappa. 
    Può intersecarne anche due contemporaneamente. 

    Restituisce una coppia in cui ognuno dei 2 elementi può assumere valori:

    IntersectionType.NoCollision -> Collisione non rilevata

    IntersectionType.IntersectionWithBlock(i,j,collisionStart,collisionEnd) ->
     collisione rilevata sul blocco di indici: (i,j) e zona di intersezione
     relativa compresa tra 0 e 1: (collisionStart, collisionEnd) 
                                                                        
  ----------------------------------------------------------------------------*)

 let checkAmmoBlocksIntersection (bullet : Bullet) =   
  let mutable collision = [| IntersectionType.NoCollision; IntersectionType.NoCollision |]
  let mutable collision_index = 0
  for i = 0 to blockMapSize do
   for j = 0 to blockMapSize do
    match gameState.BlockGrid.[i,j].makeUp with
     | CeramicBrick _ | SteelBrick _-> 
       let mutable intersection = (0.0, 0.0)

       //bullet with down movement intersect a block on his top side
       if (bullet.direction == Vector2<1>.Down) then 
        intersection <- gameState.BlockGrid.[i,j].collisionSquare.Value.Intersect(!bullet.collisionSquare, Side.Top)
       //bullet with up movement intersect a block on his bottom side
       elif (bullet.direction == Vector2<1>.Up) then
        intersection <- gameState.BlockGrid.[i,j].collisionSquare.Value.Intersect(!bullet.collisionSquare, Side.Bottom)
       //bullet with left movement intersect a block on his right side
       elif (bullet.direction == Vector2<1>.Left) then 
        intersection <- gameState.BlockGrid.[i,j].collisionSquare.Value.Intersect(!bullet.collisionSquare, Side.Right)
       //bullet with right movement intersect a block on his left side
       elif (bullet.direction == Vector2<1>.Right) then
        intersection <- gameState.BlockGrid.[i,j].collisionSquare.Value.Intersect(!bullet.collisionSquare, Side.Left)
           
       if intersection <> (0.0,0.0) then
        let intersectionPoint = match intersection with | (collisionStart,collisionEnd) ->   
                                                            collision.[collision_index] <- IntersectionType.IntersectionWithBlock(i,j,collisionStart,collisionEnd)
        collision_index <- 1;
       | _ -> ()

  //There is a couple of collision beacause a bullet can hit two blocks/enemies at the same time
  (collision.[0],collision.[1])




 (********************************************************************************
    || Descrizione della funzione:
        Verifica se il proiettile di un carro nemico colpisce uno degli altri 
        carri nemici presenti sulla mappa.

    || Parametri di ingresso:
        bullet -> lo stato di un proiettile nemico
        tankIndex -> l'indice del carro nemico che l'ha sparato

    || Restituisce:
        true -> il proiettile ha colpito un carro
        false -> il proiettile non ha colpito un carro              
                                                  
 *********************************************************************************)
 let checkEnBulletToEnTanksCollision (bullet: Bullet, tankIndex : int) =
  let mutable collision = false
  let mutable i = 0
  let lenght = (!gameState.EnemiesList).Length

  //verifico se il proiettile ha colliso con un altro dei carri nemici nella
  //Enemies List sulla base della distanza
  while i < lenght  && collision = false do
   if i <> tankIndex then
    let enemyTank = (!gameState.EnemiesList).[i]
    let bulletTankDistance = (!bullet.position - (!enemyTank).Position).LenghtSquared
    if bulletTankDistance < tankBulletBoundingDistance then
      collision <- true
      
   i <- i + 1
  collision



(********************************************************************************
    || Descrizione della funzione:
        Verifica se il proiettile di un carro nemico colpisce il 
        carro del giocatore

    || Parametri di ingresso:
        bullet -> lo stato di un proiettile nemico
   
    || Restituisce:
        true -> il proiettile ha colpito un carro
        false -> il proiettile non ha colpito un carro              
                                                  
 *********************************************************************************)
 let checkEnBulletToPlTanksCollision (bullet: Bullet) =
   let bulletTankDistance = (!bullet.position - (!gameState.PlayerTank).Position).LenghtSquared
   if bulletTankDistance < tankBulletBoundingDistance then 
    true
   else 
    false



 (********************************************************************************
    || Descrizione della funzione:
        Verifica se il proiettile sparato dal giocatore ha colpito uno dei 
        carri nemici e aggiorna la lista dei nemici colpiti con l'indice
        del carro .
    
    || Parametri di ingresso:
        bullet -> lo stato di un proiettile del giocatore
        enemiesHittedList -> riferimento alla lista contenente gli indici 
                             dei nemici colpiti
   
    || Restituisce:
        true -> il proiettile ha colpito un carro
        false -> il proiettile non ha colpito un carro                     
                                           
 *********************************************************************************)
 let checkPlBulletToTanksCollision (bullet: Bullet, enemiesHittedList : ref<List<int>>) =
  let mutable collision = false
  let mutable i = 0
  let mutable intersectionOne = (0.0, 0.0)
  let mutable intersectionTwo = (0.0, 0.0)
  let lenght = (!gameState.EnemiesList).Length
 
  //verifico se il proiettile ha colpito un carro nemico sulla base della distanza
  while i < lenght  && collision = false do
   let enemyTank = (!gameState.EnemiesList).[i]
   let bulletTankDistance = (!bullet.position - (!enemyTank).Position).LenghtSquared
   if bulletTankDistance < GameState.tankBulletBoundingDistance then 
     collision <- true
     //se il nemico è stato colpito ne aggiungo l'indice nella enemiesHittedList
     //a meno che non sia già stato aggiunto (esempio: quando viene colpito da 2 proiettili contemporaneamente)
     let mutable alreadyHit = false
     for index in enemiesHittedList.Value do
       if i = index then alreadyHit <- true
     
     if alreadyHit = false then  enemiesHittedList.Value <- i::enemiesHittedList.Value
   i <- i + 1

  collision



 (********************************************************************************
    || Descrizione della funzione:
        Verifica se un proiettile è stato colpito cercando il suo indice in 
        una lista di proiettili colpiti.
           
    || Parametri di ingresso:
        bulletIndex -> indice del proiettile
        hittedEnemyBullets -> lista di proiettili colpiti

    || Restituisce:
        true -> il proiettile è nella lista dei proiettili colpiti
        false -> il proiettile non è nella lista dei proiettili colpiti           
                                           
 *********************************************************************************)
 let bulletIsInHittedList (bulletIndex : int , hittedEnemyBullets : ref<List<int>>) = 
  let mutable i = 0
  let mutable isInHittedList = false
  let lenght = hittedEnemyBullets.Value.Length

  while i < lenght && isInHittedList = false do
   if bulletIndex = hittedEnemyBullets.Value.[i] then isInHittedList <- true
   i <- i+1

  isInHittedList



 (********************************************************************************
    || Descrizione della funzione:
        Verifica se il proiettile sparato da un carro nemico ha colliso con uno
        dei proiettili degli altri carri.
    
    || Parametri di ingresso:
        bulletPosition -> posizione del proiettile nemico
        tankIndex -> indice del carro nemico che ha sparato il proiettile


    || Restituisce:
        true -> il proiettile ha colliso con un altro proiettile nemico
        false -> il proiettile non ha colliso con un altro proiettile nemico            
                                           
 *********************************************************************************)
 let checkEnBulletToEnBulletsCollision (bulletPosition : Vector2<m>, tankIndex : int) =
  let mutable i = 0
  let mutable collision = false
  let bulletSizeSquared = bulletSize * bulletSize
  let lenght = (!gameState.EnemiesBulletList).Length

  while i < lenght && collision = false do
   let otherBullet, otherIndex = match gameState.EnemiesBulletList.Value.[i] with
                                  | bullet, index -> bullet, index
   
   if tankIndex <> otherIndex then
    let distance = (bulletPosition - otherBullet.position.Value).LenghtSquared
    if distance < bulletSizeSquared then collision <- true
   i <- i+1

  collision



(********************************************************************************
    || Descrizione della funzione:
        Verifica se il proiettile sparato dal giocatore ha colliso con uno
        dei proiettili dei carri nemici e in caso affermativo aggiorna la lista 
        dei proiettili nemici colpiti aggiungendone l'indice.
    
    || Parametri di ingresso:
        bulletPosition -> posizione del proiettile del giocatore
        hittedEnemyBullets -> lista dei proiettili nemici colpiti

    || Restituisce:
        true -> il proiettile del giocatore ha colliso con un proiettile nemico
        false -> il proiettile del giocatore non ha colliso con un proiettile nemico            
                                           
 *********************************************************************************)
 let checkPBulletEbulletsCollision (bulletPosition: Vector2<m>, hittedEnemyBullets: ref<List<int>>) =
  let mutable i = 0
  let mutable collision = false
  let bulletSizeSquared = bulletSize * bulletSize

  let lenght = (!gameState.EnemiesBulletList).Length

  while i < lenght && collision = false do
   let otherBullet = match gameState.EnemiesBulletList.Value.[i] with
                                  | bullet, index -> bullet

   let distance = (bulletPosition - otherBullet.position.Value).LenghtSquared
   if distance < bulletSizeSquared then 
    collision <- true
    hittedEnemyBullets.Value <- i::hittedEnemyBullets.Value
   i <- i+1

  collision

 (*---------------------------------------------------------------------------
    Prende in ingresso l'indice di un blocco che è stato colpito da un 
    proiettile, gli estremi della zona di intersezione, lo square di collisione
    del proiettile e la direzione da cui arrivava. 
    Verifica quindi se il proiettile ha colliso con uno dei mattonicini del
    blocco.

    Restituisce una tripla con i 2 indici del blocco colpito e un 
    BulletBrickCollision che può essere di tipo:

    Hitted -> se ha colpito uno dei mattoncini del blocco

    NotYet -> se non ha colpito uno dei mattoncini ma lo farà in futuro
              perchè sono stati rilevati dei mattoncini in posizione frontale
              al proiettile.

    NoBrickFounded -> non ha colpito dei mattoncini e non lo farà in futuro
                      perchè il blocco non contiene più mattoncini che 
                      possano ostacolare la corsa del proiettile                                                             
  ----------------------------------------------------------------------------*)

 let checkAmmoBrickCollision((rowIndex : int), (coloumnIndex : int), (intersectionStart : float ), (intersectionEnd : float), (collisionSquare : Square), (direction : Vector2<1>), (power : int)) =
  
  let mutable jStart, jEnd, jIncrement = 0, 0, 0
  let mutable iStart, iEnd, iIncrement = 0, 0, 0 
  let mutable i, j = 0, 0
  let mutable blockFinded = false;
  let c, k = rowIndex, coloumnIndex
  
  let bricksArray = Types.getBricks(gameState.BlockGrid.[c,k].makeUp); 

  match gameState.BlockGrid.[c,k].makeUp with

   | SteelBrick(x) ->     let brickSize = blockSize / 2.0
                          let mutable collision = false
                          let mutable i,j = 0,0
                          
                          let mutable side = Side.Bottom
                          if direction == Vector2<1>.Down then side <- Side.Top
                          elif direction == Vector2<1>.Left then side <- Side.Right
                          elif direction == Vector2<1>.Right then side <- Side.Left
                          
                          while i < 2 do
                           j <- 0
                           while j < 2 do
                            if((!bricksArray).[i,j]) then
                             let brickXposOffset = (float)(j - 1) * brickSize + (brickSize * 0.5)
                             let brickYposOffset = (float)(i - 1) * brickSize + (brickSize * 0.5)
                             let brickPosition = Vector2(gameState.BlockGrid.[c,k].position.X + brickXposOffset, gameState.BlockGrid.[c,k].position.Y + brickYposOffset)
                             let brickCollisionSquare = Square(brickPosition, brickSize) 
                             if brickCollisionSquare.Intersect(collisionSquare, side) <> (0.0,0.0) then //DA VEDERE BENE!!
                              if power > 1 then
                               bricksArray =* (false,i,j)
                              collision <- true
                            j <- j + 1
                           i <- i + 1
                          if collision then 
                           (BulletBrickCollision.Hitted,c,k)
                          else
                           (BulletBrickCollision.NoBrickFounded,c,k)

   | CeramicBrick(x) ->   let brickSize = blockSize / 4.0
                          let mutable blockIntersection = BlockIntersection.Internal;

                         
                          (* Calcolo su quali indici cercare i mattoni che possono aver colliso   
                            con i proiettili, in base alla direzione del proiettile e all'       
                            intersezione col blocco. Mi evita una ricerca brute force su tutti   
                            i mattoni.                                                          *)                                                
                       
                          //if bullet hits an external right brick of a block side
                          if intersectionStart > 0.75 then 
                           blockIntersection <- BlockIntersection.ExternalRight;
                           if direction == Vector2<1>.Up then
                            jStart <- 3; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 3;  iIncrement <- -1; iEnd <- 0 + iIncrement;
                           elif direction == Vector2<1>.Down then
                            jStart <- 3; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                           elif direction == Vector2<1>.Left then
                            jStart <- 3; jIncrement <- -1; jEnd <- 0 + jIncrement;
                            iStart <- 3;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                           elif direction == Vector2<1>.Right then
                            jStart <- 0; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 3;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                          //if bullet hits an external right brick and internal right brick of a block side
                          elif intersectionStart >= 0.5 then
                           blockIntersection <- BlockIntersection.ExternalRight;
                           if direction == Vector2<1>.Up then
                            jStart <- 2; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 3;  iIncrement <- -1; iEnd <- 0 + iIncrement;
                           elif direction == Vector2<1>.Down then
                            jStart <- 2; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                           elif direction == Vector2<1>.Left then
                            jStart <- 3; jIncrement <- -1; jEnd <- 0 + jIncrement;
                            iStart <- 2;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                           elif direction == Vector2<1>.Right then
                            jStart <- 0; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 2;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                          //if bullet hits one of the internal brick of a block side
                          elif intersectionStart >= 0.25 && intersectionEnd <= 0.75 then 
                           if direction == Vector2<1>.Up then
                            jStart <- 1; jIncrement <- 1; jEnd <- 2 + jIncrement;
                            iStart <- 3;  iIncrement <- -1; iEnd <- 0 + iIncrement;
                           elif direction == Vector2<1>.Down then
                            jStart <- 1; jIncrement <- 1; jEnd <- 2 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                           elif direction == Vector2<1>.Left then
                            jStart <- 3; jIncrement <- -1; jEnd <- 0 + jIncrement;
                            iStart <- 1;  iIncrement <- 1; iEnd <- 2 + iIncrement;
                           elif direction == Vector2<1>.Right then
                            jStart <- 0; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 1;  iIncrement <- 1; iEnd <- 2 + iIncrement;
                          //if bullet hits an external left brick and internal left brick of a block side
                          elif intersectionEnd > 0.25 then
                           blockIntersection <- BlockIntersection.ExternalLeft;
                           if direction == Vector2<1>.Up then
                            jStart <- 0; jIncrement <- 1; jEnd <- 1 + jIncrement;
                            iStart <- 3;  iIncrement <- -1; iEnd <- 0 + iIncrement;
                           elif direction == Vector2<1>.Down then
                            jStart <- 0; jIncrement <- 1; jEnd <- 1 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                           elif direction == Vector2<1>.Left then
                            jStart <- 3; jIncrement <- -1; jEnd <- 0 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 1 + iIncrement;
                           elif direction == Vector2<1>.Right then
                            jStart <- 0; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 1 + iIncrement;
                          //if bullet hits an external left brick of a block side
                          else
                           blockIntersection <- BlockIntersection.ExternalLeft;
                           if direction == Vector2<1>.Up then
                            jStart <- 0; jIncrement <- 1; jEnd <- 0 + jIncrement;
                            iStart <- 3;  iIncrement <- -1; iEnd <- 0 + iIncrement;
                           elif direction == Vector2<1>.Down then
                            jStart <- 0; jIncrement <- 1; jEnd <- 0 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 3 + iIncrement;
                           elif direction == Vector2<1>.Left then
                            jStart <- 3; jIncrement <- -1; jEnd <- 0 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 0 + iIncrement;
                           elif direction == Vector2<1>.Right then
                            jStart <- 0; jIncrement <- 1; jEnd <- 3 + jIncrement;
                            iStart <- 0;  iIncrement <- 1; iEnd <- 0 + iIncrement;
   
                          //looking for the first brick in front of the Bullet
                          if direction == Vector2<1>.Up || direction == Vector2<1>.Down then
                           i <- iStart;
                           while (i <> iEnd) && (not blockFinded) do
                            j <- jStart
                            while (j <> jEnd) && (not blockFinded) do
                             if (!bricksArray).[i,j] then
                              blockFinded <- true
                             j <- j + jIncrement
                            i <- i + iIncrement
                          else
                           j <- jStart;
                           while (j <> jEnd) && (not blockFinded) do
                            i <- iStart
                            while (i <> iEnd) && (not blockFinded) do
                             if (!bricksArray).[i,j] then
                              blockFinded <- true
                             i <- i + iIncrement
                            j <- j + jIncrement
  
                          if blockFinded then
                              j <- j - jIncrement
                              i <- i - iIncrement
                              let brickXposOffset = (float)((j + 1) - 2) * brickSize - (brickSize * 0.5)
                              let brickYposOffset = (float)((i + 1) - 2) * brickSize - (brickSize * 0.5)
                              let brickPosition = Vector2(gameState.BlockGrid.[c,k].position.X + brickXposOffset, gameState.BlockGrid.[c,k].position.Y + brickYposOffset)
                              let brickCollisionSquare = Square(brickPosition, brickSize)
                              //if bullet collide with a brick
                              if brickCollisionSquare.Intersect(collisionSquare, Side.Bottom) <> (0.0,0.0) then
                               //destroy bricks
                               bricksArray =* (false,i,j)
                               match blockIntersection with
                                | ExternalRight ->  if direction == Vector2<1>.Up || direction == Vector2<1>.Down then
                                                     for j = 2 to 3 do  bricksArray =* (false,i,j)
                                                    elif direction == Vector2<1>.Left || direction == Vector2<1>.Right then              
                                                     for i = 2 to 3 do  bricksArray =* (false,i,j)
                                | Internal ->  if direction == Vector2<1>.Up || direction == Vector2<1>.Down then
                                                for j = 0 to 3 do  bricksArray =* (false,i,j)
                                               elif direction == Vector2<1>.Left || direction == Vector2<1>.Right then
                                                for i = 0 to 3 do  bricksArray =* (false,i,j)
                                | ExternalLeft -> if direction == Vector2<1>.Up || direction == Vector2<1>.Down then
                                                   for j = 0 to 1 do  bricksArray =* (false,i,j)
                                                  elif direction == Vector2<1>.Left || direction == Vector2<1>.Right then 
                                                   for i = 0 to 1 do  bricksArray =* (false,i,j)
       
                              // gameState.ChangedBlockList <- updateChangedBlockList
                               (BulletBrickCollision.Hitted,c,k)
                              else
                               //if there is no collision
                               (BulletBrickCollision.NotYet,c,k)
                            else
                             //there isn't any brick that can collide with the bullet
                             (BulletBrickCollision.NoBrickFounded,c,k)

   | _ -> (BulletBrickCollision.NoBrickFounded,c,k)

 let blockHitted (bullet : Bullet , changedBlockList : ref<List<int * int>>) =
    let doYield = ref false
    let hitted = ref false

    //se il proiettile non è uscito dall mappa allora cerco le collisioni con i blocchi e i carri
    if bulletOutofMap(!bullet.position) = false then 
        let intersection = match !bullet.intersection with
                                    | (NoCollision, NoCollision) -> checkAmmoBlocksIntersection(bullet)
                                    | _ -> !bullet.intersection
        match intersection with

            //se il proiettile non ha intersecato alcun blocco lo conservo nella bulletList
            | (NoCollision,NoCollision) -> doYield.Value <- true
                               
            //se il proiettile ha intersecato 2 blocchi contemporaneamente ne verifico la collisione con i mattoncini dei due blocchi
            //se non ha colliso lo conservo
            | (IntersectionWithBlock (i,j,intersectionOneStart,intersectionOneEnd) , IntersectionWithBlock (k,l,intersectionTwoStart,intersectionTwoEnd)) -> 
                let collisions = [| checkAmmoBrickCollision(i, j, intersectionOneStart, intersectionOneEnd, !bullet.collisionSquare, bullet.direction, bullet.power);
                                checkAmmoBrickCollision(k, l, intersectionTwoStart, intersectionTwoEnd, !bullet.collisionSquare, bullet.direction, bullet.power) |]
                for i = 0 to 1 do
                    match collisions.[i] with 
                     | (BulletBrickCollision.NoBrickFounded,c,k) -> if hitted.Value = false then 
                                                                     bullet.intersection := (NoCollision, NoCollision)
                                                                     doYield.Value <- true   
                                                                                  
                     | (BulletBrickCollision.Hitted,c,k) -> changedBlockList.Value <- (c,k)::(changedBlockList.Value)
                                                            doYield.Value <- false
                                                            hitted.Value <- true    
                                                                               
                     | (BulletBrickCollision.NotYet,c,k) -> if hitted.Value = false then 
                                                             doYield.Value <- true
                                                             bullet.intersection := intersection
                                      
            //se il proiettile ha intersecato un blocco solo ne verifico ne verifico la collisione con i mattoncini del blocco
            //se non ha colliso lo conservo                             
            | (IntersectionWithBlock (i,j,intersectionStart, intersectionEnd), _ ) | ( _ , IntersectionWithBlock (i,j,intersectionStart, intersectionEnd)) -> 
                let collision = checkAmmoBrickCollision(i, j, intersectionStart, intersectionEnd, !bullet.collisionSquare, bullet.direction, bullet.power)
                match collision with
                 | (BulletBrickCollision.NoBrickFounded,c,k) -> bullet.intersection := (NoCollision, NoCollision)
                                                                doYield.Value <- true
                 | (BulletBrickCollision.Hitted,c,k) -> changedBlockList.Value <- (c,k)::(changedBlockList.Value)
                 | (BulletBrickCollision.NotYet,c,k) -> bullet.intersection := intersection
                                                        doYield.Value <- true  
                                            
    doYield.Value

   